﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;

namespace CMS.Core.Domain
{
    public class HSConfigurator
    {
        List<HSConfigEntry> _entries;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public HSConfigurator()
        {
            _entries = new List<HSConfigEntry>();
        }

        /// <summary>
        /// Constructor to read configuration from a configuration file.
        /// </summary>
        /// <param name="configFilePath">Configuration file's path.</param>
        public HSConfigurator(string configFilePath)
        {
            _entries = new List<HSConfigEntry>();

            try
            {
                // open configuration file and read its contents
                TextReader reader = new StreamReader(configFilePath);
                string configurations = reader.ReadToEnd();
                reader.Close();

                // import the configurations
                ImportConfigurations(configurations);                
            }
            catch (Exception except)
            {
                throw except;
            }
        }

        /// <summary>
        /// Import XML configuration.
        /// </summary>
        /// <param name="xmlConfigurations">Configurations in XML format.</param>
        public void ImportConfigurations(string xmlConfigurations)
        {
            // create an XML document object
            XmlDocument doc = new XmlDocument();
            doc.InnerXml = xmlConfigurations;

            // get the configuration
            XmlNodeList nodes = doc.GetElementsByTagName("session-factory");

            // if no configuration node was found, then there's nothing we can do
            if ((nodes == null) || (nodes.Count == 0))
            {
                return;
            }

            // there should only be one "configuration" node, so we only read the first one
            XmlNode config = nodes[0];

            // get all child nodes in the configuration
            XmlNodeList children = config.ChildNodes;

            // process each child node
            foreach (XmlNode child in children)
            {
                // create an HSConfigEntry for this node
                HSConfigEntry entry = new HSConfigEntry();
                entry.Name = child.Name;
                entry.Value = child.InnerText;

                if (child.Attributes != null)
                {
                    // get attributes
                    XmlAttributeCollection attributes = child.Attributes;
                    foreach (XmlAttribute attribute in attributes)
                    {
                        // add attribute to the entry
                        entry.AddAttribute(attribute.Name, attribute.Value);
                    }
                }
                // add node to entries list
                _entries.Add(entry);
            }
        }

        /// <summary>
        /// Get all entries with the matching name.
        /// </summary>
        /// <param name="name">Entry name to search for.</param>
        /// <returns>List of entries with the matching name.</returns>
        public List<HSConfigEntry> GetEntriesByName(string name)
        {
            List<HSConfigEntry> entries = new List<HSConfigEntry>();
            
            // find entries with matching name
            foreach (HSConfigEntry entry in _entries)
            {
                if (entry.Name.ToLower() == name.ToLower())
                {
                    entries.Add(entry);
                }
            }

            return entries;
        }

        /// <summary>
        /// Get all entries.
        /// </summary>
        /// <returns>List of all entries.</returns>
        public List<HSConfigEntry> GetAllEntries()
        {
            return _entries;
        }

        /// <summary>
        /// Add a new entry to the configuration.
        /// </summary>
        /// <param name="entry">The new entry to be added to the configuration.</param>
        public void AddEntry(HSConfigEntry entry)
        {
            _entries.Add(entry);
        }

        /// <summary>
        /// Remove an entry from the configuration.
        /// </summary>
        /// <param name="entry">The entry to be removed from the configuration.</param>
        public void RemoveEntry(HSConfigEntry entry)
        {
            _entries.Remove(entry);
        }

        /// <summary>
        /// Export configuration to XML.
        /// </summary>
        /// <returns>Configuration in XML format.</returns>
        public string ExportXml()
        {
            // return the XML document
            return GetXmlDocument().OuterXml;
        }

        /// <summary>
        /// Save configuration to a file.
        /// </summary>
        /// <param name="fileName">File name to save to.</param>
        public void Save(string fileName)
        {
            try
            {
                XmlDocument doc = GetXmlDocument();                
                XmlTextWriter writer = new XmlTextWriter(fileName, null);
                writer.Formatting = Formatting.Indented;
                doc.WriteTo(writer);
                writer.Close();
            }
            catch (Exception except)
            {                
                throw except;
            }
        }

        private XmlDocument GetXmlDocument()
        {
            // create an XML doc and the configuration node
            XmlDocument doc = new XmlDocument();
            XmlDeclaration declaration = doc.CreateXmlDeclaration("1.0", "utf-8", null);
            doc.AppendChild(declaration);

            // create configuration root
            XmlElement root = doc.CreateElement("hibernate-configuration");
            root.SetAttribute("xmlns", "urn:nhibernate-configuration-2.2");
            doc.AppendChild(root);

            XmlElement session = doc.CreateElement("session-factory");
            root.AppendChild(session);

            // go through each entry
            foreach (HSConfigEntry entry in _entries)
            {
                if (!entry.Name.Equals("#comment"))
                {
                    XmlElement element = doc.CreateElement(entry.Name);
                    if ((entry.Value != null) && (entry.Value.Length > 0))
                    {
                        element.InnerText = entry.Value;
                    }

                    // add attributes
                    List<HSConfigAttribute> attributes = entry.GetAllAttributes();
                    foreach (HSConfigAttribute attribute in attributes)
                    {
                        element.SetAttribute(attribute.Name, attribute.Value);
                    }

                    session.AppendChild(element);
                }
            }

            return doc;
        }
    }

    /// <summary>
    /// Contains the information of a configuration entry. For example, 
    ///   <employee fname="John" />
    /// is an entry in the configuration.
    /// </summary>
    public class HSConfigEntry
    {
        // variables
        private List<HSConfigAttribute> _attributes;
        private string _name;
        private string _value;

        // properties
        public string Name { get { return _name; } set { _name = value; } }
        public string Value { get { return _value; } set { _value = value; } }

        // methods

        /// <summary>
        /// Constructor
        /// </summary>
        public HSConfigEntry()
        {
            _attributes = new List<HSConfigAttribute>();
        }

        /// <summary>
        /// Find and retrieve the attribute's value using its name.
        /// </summary>
        /// <param name="attributeName">The attribute's name to find.</param>
        /// <returns>The value associated with the attribute name.</returns>
        public HSConfigAttribute GetAttributeByName(string name)
        {
            // search attributes list
            foreach (HSConfigAttribute attribute in _attributes)
            {
                if (attribute.Name.ToUpper() == name.ToUpper())
                {
                    return attribute;
                }
            }

            // no match found
            return null;
        }

        /// <summary>
        /// Get all attributes.
        /// </summary>
        /// <returns>List of all attributes</returns>
        public List<HSConfigAttribute> GetAllAttributes()
        {
            return _attributes;
        }

        /// <summary>
        /// Add an attribute to the current configuration entry.
        /// </summary>
        /// <param name="name">Attribute name.</param>
        /// <param name="value">Attribute value.</param>
        public void AddAttribute(string name, string value)
        {
            // create a new attribute and add to the attribute list
            HSConfigAttribute attribute = new HSConfigAttribute();
            attribute.Name = name;
            attribute.Value = value;
            _attributes.Add(attribute);
        }

        /// <summary>
        /// Add an attribute to the entry.
        /// </summary>
        /// <param name="attribute">Attribute to be added.</param>
        public void AddAttribute(HSConfigAttribute attribute)
        {
            _attributes.Add(attribute);
        }

        /// <summary>
        /// Remove the attribute from the entry.
        /// </summary>
        /// <param name="attribute">Attribute to be removed.</param>
        public void RemoveAttribute(HSConfigAttribute attribute)
        {
            _attributes.Remove(attribute);
        }

        /// <summary>
        /// Remove the attribute with the matching name.
        /// </summary>
        /// <param name="attributeName">Name of the attribute to be removed.</param>
        public void RemoveAttribute(string attributeName)
        {
            // find the attribute and remove it
            HSConfigAttribute attribute = GetAttributeByName(attributeName);
            if (attribute != null)
            {
                _attributes.Remove(attribute);
            }
        }
    }

    /// <summary>
    /// Entry attribute in configuration. For example, in the entry <employee fname="John" />
    /// fname="John" is an attribute where "fname" is the name of the attribute and "John" is 
    /// the value of the attribute.
    /// </summary>
    public class HSConfigAttribute
    {
        private string _name;
        private string _value;

        /// <summary>
        /// Name of the attribute.
        /// </summary>
        public string Name { get { return _name; } set { _name = value; } }

        /// <summary>
        /// Value of the attribute.
        /// </summary>
        public string Value { get { return _value; } set { _value = value; } }
    }
}
